﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using NUnit.Framework;
using Chefbook.DataObjects;
using System.Collections.Specialized;
using Rhino.Mocks;
using Chefbook.HelperMethods;

namespace UnitTests.DataObjectsTests
{
    [TestFixture]
    public class RecipeFixture
    {
        private MockRepository mocks;
        private Category root;
        private Recipe testRecipe;
        
        [SetUp]
        public void Init()
        {
            mocks = new MockRepository();
            root = new Category(null);
            testRecipe = new Recipe(root);
        }

        [Test]
        public void ConstructorTest()
        {
            Assert.AreEqual("", testRecipe.Name, "Constructor Name test failed");
            Assert.AreEqual("", testRecipe.Description, "Constructor Description test failed");
            Assert.AreEqual("", testRecipe.Directions, "Constructor Directions test failed");
            Assert.AreEqual(TimeSpan.Zero, testRecipe.CookTime, "Constructor CookTime test failed");
            Assert.AreEqual(TimeSpan.Zero, testRecipe.PrepTime, "Constructor PrepTime test failed");
            Assert.AreEqual(1, testRecipe.Categories.Count((c)=> c == root), "Recipe's categories didn't containe exactly one copy of Root");
            Assert.AreEqual(1, root.DirectRecipes.Count((r) => r == testRecipe), "Root's recipes didn't contain exactly one copy of testRecipe");
            Assert.AreEqual(0, testRecipe.Ingredients.Count);
        }

        [Test]
        public void RenameTest()
        {
            testRecipe.AssertPropertyChangedFired(() =>
                testRecipe.Name = "Beef Stew", "Name");
            Assert.AreEqual("Beef Stew", testRecipe.Name, "Rename test failed");
            testRecipe.AssertPropertyChangedFired(() =>
                testRecipe.Name = "Beef Stew");
        }

        [Test]
        public void RenameNullTest()
        {
            Assert.Throws<ArgumentNullException>(() => testRecipe.Name = null);
        }
        
        [Test]
        public void ChangeDescriptionTest()
        {
            testRecipe.AssertPropertyChangedFired(() =>
                testRecipe.Description = "Is a tasty beef stew", "Description");
            Assert.AreEqual("Is a tasty beef stew", testRecipe.Description, "Change Description test failed");
            testRecipe.AssertPropertyChangedFired(() =>
                testRecipe.Description = "Is a tasty beef stew");
        }

        [Test]
        public void ChangeDescriptionNullTest()
        {
            Assert.Throws<ArgumentNullException>(() => testRecipe.Description = null);
        }

        [Test]
        public void ChangeDirectionsTest()
        {
            testRecipe.AssertPropertyChangedFired(() =>
                testRecipe.Directions = "Is a tasty beef stew", "Directions");
            Assert.AreEqual("Is a tasty beef stew", testRecipe.Directions, "Change Directions test failed");
            testRecipe.AssertPropertyChangedFired(() =>
                testRecipe.Directions = "Is a tasty beef stew");
        }

        [Test]
        public void ChangeDirectionsNullTest()
        {
            Assert.Throws<ArgumentNullException>(() => testRecipe.Directions = null);
        }

        [Test]
        public void ChangeCookTimeTest()
        {
            testRecipe.AssertPropertyChangedFired(() =>
                testRecipe.CookTime = TimeSpan.FromMinutes(5.5), "CookTime");
            Assert.AreEqual(TimeSpan.FromMinutes(5.5), testRecipe.CookTime, "Change CookTime test failed");
            testRecipe.AssertPropertyChangedFired(() =>
                testRecipe.CookTime = TimeSpan.FromMinutes(5.5));
        }
        
        [Test]
        public void ChangePrepTimeTest()
        {
            testRecipe.AssertPropertyChangedFired(() =>
                testRecipe.PrepTime = TimeSpan.FromHours(1.5), "PrepTime");
            Assert.AreEqual(TimeSpan.FromHours(1.5), testRecipe.PrepTime, "Change PrepTime test failed");
            testRecipe.AssertPropertyChangedFired(() =>
                testRecipe.PrepTime = TimeSpan.FromHours(1.5));
        }

        [Test]
        public void DeleteTest()
        {
            Assert.Contains(testRecipe, root.DirectRecipes);
            testRecipe.Name = "Test";
            root.Name = "Root";
            testRecipe.Delete();
            var categories = new List<Category>(testRecipe.Categories);
            foreach (var category in categories)
            {
                Assert.IsFalse(category.DirectRecipes.Contains(testRecipe), "Categories still contain recipe");
            }
        }

        [Test]
        public void ChangeCategoryTest1()
        {
            var cat1 = new Category(root);
            var arg1 = new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add, cat1);
            var arg2 = new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Remove, root); 
            var args = testRecipe.Categories.AssertCollectionChangedFiredNTimesAndReturnArgs(() =>
                testRecipe.ChangeCategory(root, cat1));
            
            Assert.True(args.Contains(
                new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add, cat1),
                ChangedEventHelper.NotifyCollectionChangedEventArgsEqual));
            Assert.True(args.Contains(
                new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Remove, root),
                ChangedEventHelper.NotifyCollectionChangedEventArgsEqual));

            Assert.IsFalse(root.DirectRecipes.Contains(testRecipe), "Recipe not removed from old category");
            Assert.IsFalse(testRecipe.Categories.Contains(root), "Category not removed from recipe");
            Assert.Contains(cat1, testRecipe.Categories, "Recipe does not contain new category");
            Assert.Contains(testRecipe, cat1.DirectRecipes, "New category does not contain recipe");
        }

        [Test]
        public void ChangeCategoryTest2()
        {
            var cat1 = new Category(root);
            var cat2 = new Category(root);
            Assert.Throws<ArgumentException>(() => testRecipe.ChangeCategory(cat2, cat1));
        }

        [Test]
        public void ChangeCategoryNullTest1()
        {
            var cat2 = new Category(root);
            Assert.Throws<ArgumentNullException>(() => testRecipe.ChangeCategory(cat2, null));
        }

        [Test]
        public void ChangeCategoryNullTest2()
        {
            var cat1 = new Category(root);
            Assert.Throws<ArgumentNullException>(() => testRecipe.ChangeCategory(null, cat1));
        }

        [Test]
        public void AddCategory()
        {
            var cat1 = new Category(root);

            var args = testRecipe.Categories.AssertCollectionChangedFiredAndReturnArgs(() =>
                testRecipe.AddCategory(cat1));
            Assert.AreEqual(NotifyCollectionChangedAction.Add, args.Action);
            Assert.Contains(cat1, args.NewItems);
            Assert.Contains(cat1, testRecipe.Categories);
            Assert.Contains(testRecipe, cat1.DirectRecipes);
        }

        [Test]
        public void AddCategoryTwice()
        {
            var cat1 = new Category(root);
            var args = testRecipe.Categories.AssertCollectionChangedFiredAndReturnArgs(() =>
                testRecipe.AddCategory(cat1));
            Assert.AreEqual(NotifyCollectionChangedAction.Add, args.Action);
            Assert.Contains(cat1, args.NewItems);

            testRecipe.Categories.AssertCollectionChangedDidntFire(() =>
                testRecipe.AddCategory(cat1));
            
            testRecipe.AddCategory(cat1);
            Assert.AreEqual(1, cat1.DirectRecipes.Count((r) => r == testRecipe), "Recipe was added more than once");
            Assert.AreEqual(1, testRecipe.Categories.Count((c) => c == cat1), "Category was aded to recipe more than once");
        }

        [Test]
        public void AddCategoryNull()
        {
            Assert.Throws<ArgumentNullException>(()=> testRecipe.AddCategory(null));
        }

        [Test]
        public void RemoveCategory1()
        {
            var args = testRecipe.Categories.AssertCollectionChangedFiredAndReturnArgs(() =>
                testRecipe.RemoveCategory(root));
            Assert.AreEqual(NotifyCollectionChangedAction.Remove, args.Action);
            Assert.Contains(root, args.OldItems);
            Assert.False(testRecipe.Categories.Contains(root), "Recipe's list of categories still contained the category");
            Assert.False(root.DirectRecipes.Contains(testRecipe), "Category's list of recipes still contained testRecipe");
        }

        [Test]
        public void RemoveCategory2()
        {
            var cat1 = new Category(root);
            Assert.Throws<ArgumentException>(() => testRecipe.RemoveCategory(cat1));
        }

        [Test]
        public void RemoveCategoryNull()
        {
            Assert.Throws<ArgumentNullException>(() => testRecipe.RemoveCategory(null));
        }

        [Test]
        public void AddIngredient()
        {
            var ingred = new Ingredient();
            var args = testRecipe.Ingredients.AssertCollectionChangedFiredAndReturnArgs(() =>
                testRecipe.AddIngredient(ingred));
            Assert.AreEqual(NotifyCollectionChangedAction.Add, args.Action);
            Assert.Contains(ingred, args.NewItems);
            Assert.Contains(ingred, testRecipe.Ingredients);
            Assert.AreEqual(testRecipe, ingred.Recipe);
        }

        [Test]
        public void RemoveIngredient1()
        {
            var ingred = new Ingredient(testRecipe);
            var args = testRecipe.Ingredients.AssertCollectionChangedFiredAndReturnArgs(() => 
                testRecipe.RemoveIngredient(ingred));
            Assert.AreEqual(NotifyCollectionChangedAction.Remove, args.Action);
            Assert.Contains(ingred, args.OldItems);
            Assert.False(testRecipe.Ingredients.Contains(ingred), "Ingredient wasn't removed from recipe");
        }

        [Test]
        public void RemoveIngredient2()
        {
            var ingred = new Ingredient(testRecipe);
            testRecipe.RemoveIngredient(ingred);
            Assert.Throws<ArgumentException>(() => testRecipe.RemoveIngredient(ingred), "Removing ingredient that wasn't in list didn't throw ArgumentException");
        }

        [Test]
        public void RemoveIngredientNull()
        {
            Assert.Throws<ArgumentNullException>(() => testRecipe.RemoveIngredient(null));
        }

        [Test]
        public void EqualsTest()
        {
            Recipe recipe = new Recipe(new Category(null))
            {
                Name = "Hi",
                Description = "Hello",
                Directions = "Do This",
                CookTime = TimeSpan.FromMinutes(75),
                PrepTime = TimeSpan.FromMinutes(15)
            };
            Ingredient ing = new Ingredient(recipe);
            ing.Amount = 3.5f;
            ing.Name = "Apples";
            ing.Units = "";

            Ingredient ing2 = new Ingredient(recipe);
            ing.Amount = 2f;
            ing.Name = "Oranges";
            ing.Units = "Whole";

            Recipe Expected = recipe;

            recipe = new Recipe(new Category(null))
            {
                Name = "Hi",
                Description = "Hello",
                Directions = "Do This",
                CookTime = TimeSpan.FromMinutes(75),
                PrepTime = TimeSpan.FromMinutes(15)
            };
            ing = new Ingredient(recipe);
            ing.Amount = 3.5f;
            ing.Name = "Apples";
            ing.Units = "";

            ing2 = new Ingredient(recipe);
            ing.Amount = 2f;
            ing.Name = "Oranges";
            ing.Units = "Whole";

            Recipe Actual = recipe;

            Assert.True(Expected.ValueEquals(Actual));
        }

        [Test]
        public void NotEqualsTest1()
        {
            Recipe recipe = new Recipe(new Category(null))
            {
                Name = "Hi",
                Description = "Hello",
                Directions = "Do This",
                CookTime = TimeSpan.FromMinutes(75),
                PrepTime = TimeSpan.FromMinutes(15)
            };
            Ingredient ing = new Ingredient(recipe);
            ing.Amount = 3.5f;
            ing.Name = "Apples";
            ing.Units = "";

            Ingredient ing2 = new Ingredient(recipe);
            ing.Amount = 2f;
            ing.Name = "Oranges";
            ing.Units = "Whole";

            Recipe Expected = recipe;

            recipe = new Recipe(new Category(null))
            {
                Name = "Hi",
                Description = "Hello",
                Directions = "Do This",
                CookTime = TimeSpan.FromMinutes(75),
                PrepTime = TimeSpan.FromMinutes(15)
            };
            ing = new Ingredient(recipe);
            ing.Amount = 3.5f;
            ing.Name = "Apples";
            ing.Units = "";

            Recipe Actual = recipe;

            Assert.False(Expected.ValueEquals(Actual));
        }

        [Test]
        public void NotEqualsTest2()
        {
            Recipe recipe = new Recipe(new Category(null))
            {
                Name = "Hi",
                Description = "Hello",
                Directions = "Do This",
                CookTime = TimeSpan.FromMinutes(75),
                PrepTime = TimeSpan.FromMinutes(15)
            };
            Ingredient ing = new Ingredient(recipe);
            ing.Amount = 3.5f;
            ing.Name = "Apples";
            ing.Units = "";

            Ingredient ing2 = new Ingredient(recipe);
            ing.Amount = 2f;
            ing.Name = "Oranges";
            ing.Units = "Whole";

            Recipe Expected = recipe;

            recipe = new Recipe(new Category(null))
            {
                Name = "Hi",
                Description = "Hello",
                Directions = "Do This",
                CookTime = TimeSpan.FromMinutes(75),
                PrepTime = TimeSpan.FromMinutes(15)
            };
            ing = new Ingredient(recipe);
            ing.Amount = 3.5f;
            ing.Name = "Apples";
            ing.Units = "";

            ing2 = new Ingredient(recipe);
            ing.Amount = 2f;
            ing.Name = "Oranges";
            ing.Units = "Half";

            Recipe Actual = recipe;

            Assert.False(Expected.ValueEquals(Actual));
        }
    }
}
